#include <stdlib.h>

#include <adapter/bitbang.h>

#define CLOCK_IDLE() 0

bitbang::bitbang()
{
    buf_size = 0;
}

int bitbang::bb_sample()
{
    return -1;
}

int bitbang::bb_read_sample()
{
    return BB_ERROR;
}

void bitbang::bb_blink(int on)
{
}

void bitbang::bb_reset(int trst, int srst)
{
}

int bitbang::bb_read()
{
    return BB_ERROR;
}

int bitbang::bb_write(int tck, int tms, int tdi)
{
    return ERROR_FAIL;
}

void bitbang::bb_flush()
{
}

int bitbang::bitbang_state_move(int skip)
{
    int i = 0, tms = 0;
    uint8_t tms_scan = tap_get_tms_path(tap_get_state(), tap_get_end_state());
    int tms_count = tap_get_tms_path_len(tap_get_state(), tap_get_end_state());

    for (i = skip; i < tms_count; i++)
    {
        tms = (tms_scan >> i) & 1;
        if (bb_write(0, tms, 0) != ERROR_OK)
            return ERROR_FAIL;
        if (bb_write(1, tms, 0) != ERROR_OK)
            return ERROR_FAIL;
    }
    if (bb_write(CLOCK_IDLE(), tms, 0) != ERROR_OK)
        return ERROR_FAIL;

    tap_set_state(tap_get_end_state());
    return ERROR_OK;
}

void bitbang::bitbang_end_state(tap_state_t state)
{
    //assert(tap_is_state_stable(state));
    tap_set_end_state(state);
}

int bitbang::bitbang_scan(bool ir_scan, enum scan_type type, uint8_t *buffer, unsigned scan_size)
{
    tap_state_t saved_end_state = tap_get_end_state();
    unsigned bit_cnt;

    if (!((!ir_scan &&
           (tap_get_state() == TAP_DRSHIFT)) ||
          (ir_scan && (tap_get_state() == TAP_IRSHIFT))))
    {
        if (ir_scan)
            bitbang_end_state(TAP_IRSHIFT);
        else
            bitbang_end_state(TAP_DRSHIFT);

        if (bitbang_state_move(0) != ERROR_OK)
            return ERROR_FAIL;
        bitbang_end_state(saved_end_state);
    }

    size_t buffered = 0;
    for (bit_cnt = 0; bit_cnt < scan_size; bit_cnt++)
    {
        int tms = (bit_cnt == scan_size - 1) ? 1 : 0;
        int tdi;
        int bytec = bit_cnt / 8;
        int bcval = 1 << (bit_cnt % 8);

        /* if we're just reading the scan, but don't care about the output
         * default to outputting 'low', this also makes valgrind traces more readable,
         * as it removes the dependency on an uninitialised value
         */
        tdi = 0;
        if ((type != SCAN_IN) && (buffer[bytec] & bcval))
            tdi = 1;

        if (bb_write(0, tms, tdi) != ERROR_OK)
            return ERROR_FAIL;

        if (type != SCAN_OUT)
        {
            if (buf_size)
            {
                if (bb_sample() != ERROR_OK)
                    return ERROR_FAIL;
                buffered++;
            }
            else
            {
                switch (bb_read())
                {
                case BB_LOW:
                    buffer[bytec] &= ~bcval;
                    break;
                case BB_HIGH:
                    buffer[bytec] |= bcval;
                    break;
                default:
                    return ERROR_FAIL;
                }
            }
        }

        if (bb_write(1, tms, tdi) != ERROR_OK)
            return ERROR_FAIL;

        if (type != SCAN_OUT && buf_size &&
            (buffered == buf_size ||
             bit_cnt == scan_size - 1))
        {
            for (unsigned i = bit_cnt + 1 - buffered; i <= bit_cnt; i++)
            {
                switch (bb_read_sample())
                {
                case BB_LOW:
                    buffer[i / 8] &= ~(1 << (i % 8));
                    break;
                case BB_HIGH:
                    buffer[i / 8] |= 1 << (i % 8);
                    break;
                default:
                    return ERROR_FAIL;
                }
            }
            buffered = 0;
        }
    }

    if (tap_get_state() != tap_get_end_state())
    {
        /* we *KNOW* the above loop transitioned out of
         * the shift state, so we skip the first state
         * and move directly to the end state.
         */
        if (bitbang_state_move(1) != ERROR_OK)
            return ERROR_FAIL;
    }
    return ERROR_OK;
}

int bitbang::bitbang_runtest(int num_cycles)
{
    int i;

    tap_state_t saved_end_state = tap_get_end_state();

    /* only do a state_move when we're not already in IDLE */
    if (tap_get_state() != TAP_IDLE)
    {
        bitbang_end_state(TAP_IDLE);
        if (bitbang_state_move(0) != ERROR_OK)
            return ERROR_FAIL;
    }

    /* execute num_cycles */
    for (i = 0; i < num_cycles; i++)
    {
        if (bb_write(0, 0, 0) != ERROR_OK)
            return ERROR_FAIL;
        if (bb_write(1, 0, 0) != ERROR_OK)
            return ERROR_FAIL;
    }
    if (bb_write(CLOCK_IDLE(), 0, 0) != ERROR_OK)
        return ERROR_FAIL;

    /* finish in end_state */
    bitbang_end_state(saved_end_state);
    if (tap_get_state() != tap_get_end_state())
        if (bitbang_state_move(0) != ERROR_OK)
            return ERROR_FAIL;

    return ERROR_OK;
}

int bitbang::bitbang_path_move(struct pathmove_command *cmd)
{
    int num_states = cmd->num_states;
    int state_count;
    int tms = 0;

    state_count = 0;
    while (num_states)
    {
        if (tap_state_transition(tap_get_state(), false) == cmd->path[state_count])
            tms = 0;
        else if (tap_state_transition(tap_get_state(), true) == cmd->path[state_count])
            tms = 1;
        else
        {
            LOG_ERROR("BUG: %s -> %s isn't a valid TAP transition",
                      tap_state_name(tap_get_state()),
                      tap_state_name(cmd->path[state_count]));
            return ERROR_FAIL;
        }

        if (bb_write(0, tms, 0) != ERROR_OK)
            return ERROR_FAIL;
        if (bb_write(1, tms, 0) != ERROR_OK)
            return ERROR_FAIL;

        tap_set_state(cmd->path[state_count]);
        state_count++;
        num_states--;
    }

    if (bb_write(CLOCK_IDLE(), tms, 0) != ERROR_OK)
        return ERROR_FAIL;

    tap_set_end_state(tap_get_state());

    return ERROR_OK;
}

int bitbang::bitbang_stableclocks(int num_cycles)
{
    int tms = (tap_get_state() == TAP_RESET ? 1 : 0);
    int i;

    /* send num_cycles clocks onto the cable */
    for (i = 0; i < num_cycles; i++)
    {
        if (bb_write(1, tms, 0) != ERROR_OK)
            return ERROR_FAIL;
        if (bb_write(0, tms, 0) != ERROR_OK)
            return ERROR_FAIL;
    }

    return ERROR_OK;
}

/**
 * Clock a bunch of TMS (or SWDIO) transitions, to change the JTAG
 * (or SWD) state machine.
 */
int bitbang::bitbang_execute_tms(struct jtag_command *cmd)
{
    unsigned num_bits = cmd->cmd.tms->num_bits;
    const uint8_t *bits = cmd->cmd.tms->bits;

    LOG_DEBUG_IO("TMS: %d bits", num_bits);

    int tms = 0;
    for (unsigned i = 0; i < num_bits; i++)
    {
        tms = ((bits[i / 8] >> (i % 8)) & 1);
        if (bb_write(0, tms, 0) != ERROR_OK)
            return ERROR_FAIL;
        if (bb_write(1, tms, 0) != ERROR_OK)
            return ERROR_FAIL;
    }
    if (bb_write(CLOCK_IDLE(), tms, 0) != ERROR_OK)
        return ERROR_FAIL;

    return ERROR_OK;
}

int bitbang::bitbang_execute_reset(struct jtag_command *cmd)
{
    bb_reset(cmd->cmd.reset->trst, cmd->cmd.reset->srst);

    return ERROR_OK;
}

int bitbang::execute(struct jtag_command *cmd)
{
    int ret = ERROR_OK;

    switch (cmd->type)
    {
    case JTAG_RUNTEST:
    {
        LOG_DEBUG_IO("runtest %i cycles, end in %s",
                     cmd->cmd.runtest->num_cycles,
                     tap_state_name(cmd->cmd.runtest->end_state));

        bitbang_end_state(cmd->cmd.runtest->end_state);
        ret = bitbang_runtest(cmd->cmd.runtest->num_cycles);
    }
    break;
    case JTAG_STABLECLOCKS:
    {
        /* this is only allowed while in a stable state.  A check for a stable
           * state was done in jtag_add_clocks()
        */
        ret = bitbang_stableclocks(cmd->cmd.stableclocks->num_cycles);
    }
    break;
    case JTAG_TLR_RESET:
    {
        LOG_DEBUG_IO("statemove end in %s",
                     tap_state_name(cmd->cmd.statemove->end_state));

        bitbang_end_state(cmd->cmd.statemove->end_state);
        ret = bitbang_state_move(0);
    }
    break;
    case JTAG_PATHMOVE:
    {
        LOG_DEBUG_IO("pathmove: %i states, end in %s",
                     cmd->cmd.pathmove->num_states,
                     tap_state_name(cmd->cmd.pathmove->path[cmd->cmd.pathmove->num_states - 1]));

        ret = bitbang_path_move(cmd->cmd.pathmove);
    }
    break;
    case JTAG_SCAN:
    {
        uint8_t *buffer;
        int scan_size;
        enum scan_type type;

        bitbang_end_state(cmd->cmd.scan->end_state);
        scan_size = jtag_build_buffer(cmd->cmd.scan, &buffer);

        LOG_DEBUG_IO("%s scan %d bits; end in %s",
                     (cmd->cmd.scan->ir_scan) ? "IR" : "DR",
                     scan_size,
                     tap_state_name(cmd->cmd.scan->end_state));

        type = jtag_scan_type(cmd->cmd.scan);
        if (bitbang_scan(cmd->cmd.scan->ir_scan, type, buffer,
                         scan_size) != ERROR_OK)
        {
            ret = ERROR_FAIL;
            goto _scanend;
        }

        if (jtag_read_buffer(buffer, cmd->cmd.scan) != ERROR_OK)
            ret = ERROR_JTAG_QUEUE_FAILED;

    _scanend:
        free(buffer);
    }
    break;
    case JTAG_SLEEP:
    {
    }
    break;
    case JTAG_TMS:
    {
        ret = bitbang_execute_tms(cmd);
    }
    break;
    case JTAG_RESET:
    {
        ret = bitbang_execute_reset(cmd);
    }
    break;
    default:
    {
        LOG_ERROR("BUG: unknown JTAG command type encountered");
        ret = ERROR_FAIL;
    }
    break;
    }

    return ret;
}

int bitbang::execute_queue(struct list_head *chead)
{
    int ret;
    struct jtag_command *cmd;

    bb_blink(1);

    __list_for_each_entry(cmd, chead, node, struct jtag_command)
    {
        ret = execute(cmd);
        if (ret != 0)
        {
            break;
        }
    }

    bb_blink(0);
    bb_flush();

    return ret;
}
